package com.xhj.mybatis.plugin;

import com.xhj.mybatis.executor.Executor;
import com.xhj.mybatis.mapping.BoundSql;
import com.xhj.mybatis.mapping.MappedStatement;
import com.xhj.mybatis.session.ResultHandler;
import com.xhj.mybatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * ExecutorInterceptor
 *
 * @author XJks
 * @description 处理 Executor 类的拦截器插件类，用于在执行 SQL 语句时记录日志信息。
 */
@Intercepts({
        /**
         * 拦截 Executor 的 query 方法
         * TODO:这里只能拦截被外部类(非Executor.class)调用的方法，无法拦截内部调用的方法，详细解释见 {@link com.xhj.mybatis.executor.BaseExecutor#query(MappedStatement, Object, RowBounds, ResultHandler)} 方法中的注释。
         */
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        // 拦截 Executor 的 update 方法
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class ExecutorInterceptor implements Interceptor {

    /**
     * 日志记录器
     */
    private static final Logger log = LoggerFactory.getLogger(ExecutorInterceptor.class);

    /**
     * 方法处理器映射表
     */
    private static final Map<Method, InterceptMethodHandler> methodHandlerMap;

    /**
     * 静态代码块，初始化方法处理器映射表
     */
    static {
        methodHandlerMap = new HashMap<>();
        try {
            // 处理 query 方法
            Method query = Executor.class.getDeclaredMethod("query", MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class);
            methodHandlerMap.put(query, ExecutorInterceptor::handleQuery);
            // 处理 update 方法
            Method update = Executor.class.getDeclaredMethod("update", MappedStatement.class, Object.class);
            methodHandlerMap.put(update, ExecutorInterceptor::handleUpdate);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 重写 intercept 方法，添加日志记录功能
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取被拦截的方法
        Method method = invocation.getMethod();
        // 从映射表中获取对应的方法处理器
        InterceptMethodHandler handler = methodHandlerMap.get(method);
        // 如果找到了对应的处理器，则调用处理器进行处理
        if (handler != null) {
            return handler.handle(invocation);
        }
        // 如果没有对应的处理器，则直接执行原方法
        return invocation.proceed();
    }

    /**
     * 处理 query 方法的逻辑
     *
     * @param invocation 调用信息
     * @return 方法执行结果
     * @throws Throwable 可能抛出的异常
     */
    private static Object handleQuery(Invocation invocation) throws Throwable {
        // 获取方法参数
        Object[] args = invocation.getArgs();
        // 获取 MappedStatement
        MappedStatement ms = (MappedStatement) args[0];
        // 获取参数对象
        Object parameterObject = args[1];
        // 获取 BoundSql 对象
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 格式化 SQL，去除多余的空白字符
        String sql = boundSql.getSql().replaceAll("\\s+", " ").trim();
        // 声明返回结果对象
        Object result = null;
        // 记录开始时间
        long start = System.nanoTime();
        try {
            // 执行被拦截的方法
            result = invocation.proceed();
            // 返回结果
            return result;
        } finally {
            // 计算执行时间，单位为毫秒
            long executionTime = (System.nanoTime() - start) / 1_000_000;
            // 获取参数对象的简要视图信息
            String parameterObjectView = InterceptorUtil.getParameterObjectView(boundSql, ms.getConfiguration());
            // 获取结果的简要视图信息
            String resultView = InterceptorUtil.getResultView(result);
            // 记录日志信息
            log.info("[Executor] query | executionTime={}ms | sql=\"{}\" | parameterObjectView={} | resultView={}", executionTime, sql, parameterObjectView, resultView);
        }
    }

    /**
     * 处理 update 方法的逻辑
     *
     * @param invocation 调用信息
     * @return 方法执行结果
     * @throws Throwable 可能抛出的异常
     */
    private static Object handleUpdate(Invocation invocation) throws Throwable {
        // 声明返回结果对象
        Object result = null;
        // 记录开始时间
        long start = System.nanoTime();
        try {
            // 执行被拦截的方法
            result = invocation.proceed();
            // 返回结果
            return result;
        } finally {
            // 计算执行时间，单位为毫秒
            long executionTime = (System.nanoTime() - start) / 1_000_000;
            // 记录日志信息
            log.info("[Executor] update | executionTime={}ms | rowCount={}", executionTime, result);
        }
    }
}
